/*
**    Copyright (C) 2003-2011 Institute for Systems Biology
**                            Seattle, Washington, USA.
**
**    This library is free software; you can redistribute it and/or
**    modify it under the terms of the GNU Lesser General Public
**    License as published by the Free Software Foundation; either
**    version 3 of the License, or (at your option) any later version.
**
**    This library is distributed in the hope that it will be useful,
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
**    Lesser General Public License for more details.
**
**    You should have received a copy of the GNU Lesser General Public
**    License along with this library; if not, write to the Free Software
**    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
*/

package org.systemsbiology.util;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import org.systemsbiology.iteration.IteratorConverter;

/**
 * A collection of up/down counters indexed by key, dynamically creating new counters on request.
 * @author anorberg
 *
 * @param <T> Key type. Must be safely hashable.
 */
public class Clicker<T> implements Iterable<Pair<T, Integer>>, Serializable{

	private HashMap<T, UpDown> data;

    /**
     * Constructs a Clicker with all values initially zero (or, more accurately, missing).
     */
	public Clicker(){
		data = new HashMap<T, UpDown>();
	}

    /**
     * Copy constructor, with type flexibility.
     * @param source Clicker using a key type convertible to the key type of the Clicker we intend to construct.
     */
    public Clicker(Clicker<? extends T> source){
        data = new HashMap<T, UpDown>();
        //copy the UpDowns to avoid fastening them together
        for(Map.Entry<? extends T, UpDown> value : source.data.entrySet()){
            data.put(value.getKey(), new UpDown(value.getValue().getValue()));
        }
    }

	/**
	 * Iterates over all nonzero values. No guaranteed order.
	 * @return Iterator that yields all nonzero counts.
	 */
	@Override
	public Iterator<Pair<T, Integer>> iterator() {
		return new IteratorConverter<Map.Entry<T, UpDown>, Pair<T, Integer>>(data.entrySet().iterator()){
			protected Pair<T, Integer> convert(Entry<T, UpDown> next) {
				return new Pair<T, Integer>(next.getKey(), Integer.valueOf(next.getValue().getValue()));
			}
			
		};
	}
	
	/**
	 * Increments the value of the counter associated with the key,
	 * then returns the incremented value. (functionally, a preincrement)
	 * @param key Identifier of counter to raise. If the counter doesn't exist,
	 *            a new counter will be immediately created with a value of 0,
	 *            which will be immediately raised to 1.
	 * @return New value of the counter for that key.
	 */
	public int up(T key){
		UpDown click = data.get(key);
		if(click == null){
			click = new UpDown();
			data.put(key, click);
		}
		return click.up();
	}
	
	/**
	 * Decrements the value of the counter associated with the key,
	 * then returns the decremented value. (functionally, a predecrement)
	 * @param key Identifier of counter to lower. If the counter doesn't exist,
	 *            a new counter will be immediately created with a value of 0,
	 *            which will be immediately raised to -1.
	 * @return New value of the counter for that key.
	 */
	public int down(T key){
		UpDown click = data.get(key);
		if(click == null){
			click = new UpDown();
			data.put(key, click);
		}
		return click.down();
	}
	
	/**
	 * Sets the value of a counter to 0.
	 * @param key Counter to zero out.
	 */
	public void zero(T key){
		data.remove(key); //missing value equivalent to a zero, by design and contract
	}

    /**
     * Get the total number of clicks, in linear time relative to the contained map
     * @return long total number of clicks
     */
    public synchronized long total() {
        long count = 0;
        for (Map.Entry<T,UpDown> val: data.entrySet()) {
            count += val.getValue().getValue();   
        }
        return count;
    }
	
	/**
	 * Retrieves the value of a counter. A counter that doesn't exist is defined
	 * to have (and in all ways behaves like it has) a value of 0.
	 * @param key Counter to seek.
	 * @return Value of that counter.
	 */
	public int get(T key){
		UpDown click = data.get(key);
		if(click == null){return 0;}
		return click.getValue();
	}

    /**
     * Set a value for a given key.
     * @param key Key to change the value for.
     * @param value Value to write.
     */
    public void put(T key, int value){
        data.put(key, new UpDown(value));
    }

    /**
     * Merges in the contents of another Clicker. Its values are added to the values of this Clicker.
     * The passed Clicker is unchanged; this one is modified, generally heavily.
     * @param mergeIn Clicker to add the values of.
     */
    public void add(Clicker<? extends T> mergeIn){
        for(Map.Entry<? extends T, UpDown> counter : mergeIn.data.entrySet()){
            UpDown value = data.get(counter.getKey());
            if(value != null){
                value.setValue(value.getValue() + counter.getValue().getValue());
            } else {
                data.put(counter.getKey(), new UpDown(counter.getValue().getValue()));
            }
        }
    }
	
	/**
	 * A mutable int in a box designed to be incremented and decremented.
	 * @author anorberg
	 */
	private static class UpDown implements Serializable{
		/**
		 * Stored value.
		 */
		private int value;
		public UpDown(){
			value = 0;
		}

		public UpDown(int n){
			value = n;
		}
		/**
		 * Increments the stored value, then returns the new value.
		 * @return The value of this object, after the increment has occured.
		 */
		public int up(){
			return ++value;
		}
		/**
		 * Decrements the stored value, then returns the new value.
		 * @return The value of this object, after the decrement has occured.
		 */
		public int down(){
			return --value;
		}
		public int getValue(){
			return value;
		}
		public void setValue(int n){
			value = n;
		}
		/**
		 * Sets the value of this object to zero.
		 */
		@SuppressWarnings("unused")
		public void zero(){
			setValue(0);
		}
	}
}
